#pragma once

#include "awaitable.hpp"
#include "context.hpp"
#include "pw_async2/context.h"
#include "pw_async2/poll.h"
#include "pw_async2/waker.h"
#include "pw_containers/intrusive_list.h"
#include "pw_function/function.h"
#include "pw_result/result.h"
#include <tuple>

namespace coro {
template <typename T>
struct [[nodiscard]] EventFuture : public T::FutureListType::Item {
  using DataType = typename T::DataType;
  explicit EventFuture(T &t) : m_event(&t) {
    m_event->m_future_list.push_front(*this);
  }
  EventFuture(EventFuture &&other) : m_event(nullptr) {
    *this = std::move(other);
  }
  EventFuture(const EventFuture &) = delete;
  EventFuture &operator=(EventFuture &&other) {
    m_event = other.m_event;
    if (not other.unlisted()) {
      auto previous = m_event->m_future_list.before_begin();
      while (&*std::next(previous) != &other) {
        previous++;
      }
      other.unlist(&*previous);
      m_event->m_future_list.insert_after(previous, *this);
    }
    return *this;
  }
  EventFuture &operator=(const EventFuture &) = delete;
  ~EventFuture() {
    if (this->unlisted()) {
      return;
    }
    if (&m_event->m_future_list.front() == this) {
      m_event->m_future_list.pop_front();
    } else {
      m_event->m_future_list.remove(*this);
    }
  }
  auto Pend(pw::async2::Context &cx) -> pw::async2::Poll<DataType> {
    using namespace pw::async2;

    if (this->unlisted()) {
      return Ready(m_event->m_data);
    }
    PW_ASYNC_STORE_WAKER(cx, m_waker, "Event waker");
    return Pending();
  }
  auto Resume() { std::move(m_waker).Wake(); }

private:
  T *m_event{nullptr};
  pw::async2::Waker m_waker{};
};

template <typename... TData> struct Event {
  using DataType =
      std::conditional_t<(sizeof...(TData) != 1), std::tuple<TData...>,
                         std::tuple_element_t<0, std::tuple<TData...>>>;
  using FutureType = EventFuture<Event>;
  using FutureListType = pw::IntrusiveList<FutureType>;
  Event() = default;
  auto operator()(TData... data) -> void {
    auto tuple_data = std::make_tuple(std::forward<TData>(data)...);
    if constexpr (sizeof...(TData) > 1) {
      m_data = tuple_data;
    } else {
      m_data = std::get<0>(tuple_data);
    }
    this->Resume();
  }
  [[nodiscard]] auto operator*() -> FutureType { return FutureType{*this}; }
  [[nodiscard]] auto Get() -> FutureType { return FutureType(*this); }
  auto Set(TData... data) -> void { this->operator()(data...); }
  auto Resume() {
    while (not m_future_list.empty()) {
      m_future_list.front().Resume();
      m_future_list.pop_front();
    }
  }
  auto Untill(coro::Context &,
              pw::Function<bool(DataType)> &&predicate) -> coro::AsyncStatus {
    while (true) {
      if (const auto ret = co_await this->operator*(); predicate(ret) == true) {
        co_return pw::OkStatus();
      }
    }
  }

protected:
  friend FutureType;
  DataType m_data;
  FutureListType m_future_list;
};
template <> struct Event<> {
  using DataType = std::tuple<>;
  using FutureType = EventFuture<Event>;
  using FutureListType = pw::IntrusiveList<FutureType>;
  Event() = default;
  auto operator()() -> void { this->Resume(); }
  [[nodiscard]] auto operator*() -> FutureType { return FutureType{*this}; }
  auto Resume() -> void {
    while (not m_future_list.empty()) {
      m_future_list.front().Resume();
      m_future_list.pop_front();
    }
  }
  auto Set() -> void { this->operator()(); }
  [[nodiscard]] auto Get() -> FutureType { return FutureType(*this); }

  auto Untill(coro::Context &,
              pw::Function<bool(DataType)> &&predicate) -> coro::AsyncStatus {
    while (true) {
      if (const auto ret = co_await this->operator*(); predicate(ret) == true) {
        co_return pw::OkStatus();
      }
    }
  }

private:
  friend FutureType;
  DataType m_data;
  FutureListType m_future_list;
};

namespace internal {

template <std::size_t N> struct String {
  consteval explicit(false) String(char const (&str)[N]) {
    for (auto i = std::size_t{}; i < N; ++i) {
      value[i] = str[i];
    }
  }

  consteval explicit(true) String(char const *str, std::size_t sz) {
    for (auto i = std::size_t{}; i < sz; ++i) {
      value[i] = str[i];
    }
  }

  constexpr static std::integral_constant<std::size_t, N - 1U> size{};
  constexpr static std::integral_constant<bool, N == 1U> empty{};

  constexpr explicit(true) operator std::string_view() const {
    return std::string_view{std::cbegin(value), size()};
  }

  std::array<char, N> value{};
};
template <internal::String name, typename... TArgs>
class EventSingleton : public Event<TArgs...> {
public:
  static auto &Instance() {
    static EventSingleton instance;
    return instance;
  }

private:
  EventSingleton(const EventSingleton &) = delete;
  EventSingleton(const EventSingleton &&) = delete;
  EventSingleton &operator=(const EventSingleton &) = delete;
  EventSingleton &operator=(const EventSingleton &&) = delete;
  EventSingleton() = default;
};
} // namespace internal

#define CO_EVT(name, ...)                                                      \
  [[maybe_unused]] auto &name =                                                \
      coro::internal::EventSingleton<#name __VA_OPT__(, )                      \
                                         __VA_ARGS__>::Instance()
template <internal::String name> inline auto EventLauch(auto... args) -> void {
  auto &event = internal::EventSingleton<name, decltype(args)...>::Instance();
  event(args...);
}

#define CO_EVT_DECLARE(name, ...)                                              \
  inline static auto &name = coro::internal::EventSingleton<#name __VA_OPT__(  \
      , ) __VA_ARGS__>::Instance()
#define CO_EVT_LAUCH(name, ...) coro::EventLauch<#name>(__VA_ARGS__)
} // namespace coro